Skip to content

Renew public client TTL on read in Redis store#5469

Open
mani-muon wants to merge 1 commit into
stacklok:mainfrom
mani-muon:mani/authserver-renew-public-client-ttl
Open

Renew public client TTL on read in Redis store#5469
mani-muon wants to merge 1 commit into
stacklok:mainfrom
mani-muon:mani/authserver-renew-public-client-ttl

Conversation

@mani-muon

@mani-muon mani-muon commented Jun 9, 2026

Copy link
Copy Markdown

Summary

Public clients registered through Dynamic Client Registration (DCR) get DefaultPublicClientTTL (30 days) stamped on their Redis registration at registration time, and GetClient only reads it back. Because the read path never renews the TTL, expiry is a fixed window from registration rather than from last use: an actively-used public client — e.g. a long-lived MCP desktop/IDE client that caches its client_id indefinitely — is evicted 30 days after it registered regardless of how often it is used. Its next token request then fails at the token endpoint with invalid_client, and the only recovery is to fully re-register the client (which silently recurs ~monthly).

  • Renew the registration TTL to DefaultPublicClientTTL on a successful GetClient for public clients, turning expiry into a sliding window of inactivity.
  • Abandoned public clients still expire after the inactivity window, preserving the anti-bloat intent of RegisterClient.
  • Confidential clients are stored without a TTL and are left untouched.
  • Renewal is best-effort: a failure is logged (slog.Warn) and does not fail the lookup.

Type of change

  • Bug fix
  • New feature
  • Refactoring (no behavior change)
  • Dependency update
  • Documentation
  • Other (describe):

Test plan

  • Unit tests (task test)
  • E2E tests (task test-e2e)
  • Linting (task lint-fix)
  • Manual testing (describe below)

Ran the pkg/authserver/storage unit tests with -race (the Taskfile's go test invocation, scoped to that package) — all pass. Verified the new test fails without the fix (the aged TTL is not renewed) and passes with it, and that confidential clients gain no TTL on read. Ran go vet on the package. golangci-lint was not run locally; leaving the full lint to CI.

Does this introduce a user-facing change?

Yes. Long-lived public OAuth/MCP clients (e.g. Claude Desktop, Cursor, VS Code) backed by the Redis storage no longer stop working ~30 days after they were first authorized; an in-use client's registration stays valid as long as it keeps being used. No configuration change is required. The in-memory backend is unaffected.

Special notes for reviewers

  • The in-memory backend (memory.go) stores clients in a bare map with no TTL, so public clients there never expire — the two backends already diverge on this and this PR does not change in-memory behavior.
  • GetClient issues an extra EXPIRE per call for public clients. If the per-request round-trip is a concern, a natural follow-up is to renew only when the remaining TTL is below a threshold; I kept this change minimal and can add that if preferred.

Public clients registered via DCR get DefaultPublicClientTTL stamped on
their Redis registration at registration time, and GetClient only reads
it back. The TTL is therefore a fixed window from registration: an
actively-used public client (e.g. a long-lived MCP desktop/IDE client
that caches its client_id) is evicted DefaultPublicClientTTL after it
registered, regardless of use. Its next token request then fails at the
token endpoint with invalid_client, and the only recovery is to fully
re-register the client.

Renew the TTL to DefaultPublicClientTTL on a successful GetClient for
public clients, turning expiry into a sliding window of inactivity.
Abandoned clients still expire after the window, preserving the
anti-bloat intent of RegisterClient; confidential clients have no TTL
and are left untouched. Renewal is best-effort: a failure is logged and
does not fail the lookup.

The in-memory backend stores clients without a TTL, so it is unaffected.

Add a unit test covering both the public-client renewal and the
confidential-client no-TTL invariant.

Signed-off-by: Mani Japra <mani@muonspace.com>
@mani-muon mani-muon marked this pull request as ready for review June 9, 2026 04:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant